#include "Mesh.h"
#include "../../Helix.h"
#include "../../HObject.h"

#include <vector>
#include <map>
#include <string>


using namespace std;

namespace helix
{

	Mesh::Mesh(int vertsPerFace, int faceDataSize, unsigned int* faceIndexData, int vertexDataSize, float* vertexData) : HObject()
	{
		this->vertsPerFace = vertsPerFace;
		faces = faceDataSize/vertsPerFace;
		verts = vertexDataSize/3;
		customBuffers = 0;

		bufferKeys.clear();

		faceData = new vector<unsigned int>();
		for(unsigned int i=0;i<faceDataSize;i++)
		{
			unsigned int thisIndex = faceIndexData[i];
			faceData->push_back(thisIndex);
		}

		//Create a new VertexArrayBuffer and put it in the map of buffers
		addDataBuffer(VERTEX_BUFFER,vertexData,3,"Vertex Data");
	}

	Mesh::~Mesh()
	{
		delete faceData;

		for(int i=0;i<bufferKeys.size();i++)
			deleteDataBuffer(bufferKeys[i]);
	}

	bool Mesh::attachDataBuffer(float *data, int bufferType)
	{
		//detach the buffer if it already exists
		if(isBufferAvailable(bufferType)){
			Log().Println("Data buffer already exists.  Deleting old buffer and replacing it with the new one.");
			detachDataBuffer(bufferType);
		}

		//if an invalid bufferType is specified, then an error has occured
		//This is mainly so that the user can't attach vertex data
		if(bufferType<1)
		{
			Log().Error("Could not create a buffer of type %d because that type does not exist.",bufferType);
			return false;
		}

		int partsPerVert = 3;
		if(bufferType<3)
			partsPerVert = bufferType;

		//Set the name of the buffer.
		string buffName="";
		switch(bufferType)
		{
			case TEX_COORD_1D_BUFFER: buffName="Texture Coordinates (1D)"; break;
			case TEX_COORD_2D_BUFFER: buffName="Texture Coordinates (2D)"; break;
			case TEX_COORD_3D_BUFFER: buffName="Texture Coordinates (2D)"; break;
			case RGB_COLOR_BUFFER: buffName="Vertex Colors (RGB)"; break;
			case RGBA_COLOR_BUFFER: partsPerVert=4; buffName="Vertex Colors (RGBA)"; break;
			case NORMAL_BUFFER: buffName="Surface Normals"; break;
			case TANGENT_BUFFER: buffName="Surface Tangents"; break;
			case BITANGENT_BUFFER: buffName="Surface BiTangents"; break;
		}

		addDataBuffer(bufferType, data, partsPerVert, buffName.c_str());

		return true;
	}

	int Mesh::attachCustomDataBuffer(float *data, int partsPerVertex, const char *buffName)
	{
		int bufferType = CUSTOM_BUFFER_KEY_START + customBuffers;
		addDataBuffer(bufferType, data, partsPerVertex, buffName);

		customBuffers++;

		//Return the key (id) of the buffer
		return bufferType;
	}

	bool Mesh::detachDataBuffer(int bufferType)
	{
		if(bufferType<1)
		{
			Log().Error("Cannot detach databuffer of type %d because it does not exist or is not detachable.",bufferType);
			return false;
		}
		
		return deleteDataBuffer(bufferType);
	}

	void Mesh::addDataBuffer(int buffType, float *data, int partsPerVertex, const char *buffName)
	{
		//Create a new buffer
		VertexArrayBuffer *vab = new VertexArrayBuffer(data, partsPerVertex * verts, partsPerVertex, buffName);
		dataBuffers[buffType]=vab;

		bufferKeys.push_back(buffType);
	}

	bool Mesh::deleteDataBuffer(int bufferType)
	{
		if(!isBufferAvailable(bufferType))
			return false;

		delete dataBuffers[bufferType];

		dataBuffers[bufferType]=NULL;

		for(int i=0;i<bufferKeys.size();i++)
		{
			if(bufferKeys[i] == bufferType)
			{
                bufferKeys.erase(bufferKeys.begin() + i);
				i=bufferKeys.size()+1;
			}
		}

		return true;
	}

	bool Mesh::replaceDataBuffer(int bufferType, float *data)
	{
		if(!isBufferAvailable(bufferType))
			return false;

		VertexArrayBuffer *vab = dataBuffers[bufferType];
		delete vab->buffer;
		vab->buffer = new vector<float>();
		vab->buffer->clear();

		int dataSize = vab->partsPerVertex * verts;

		for(int i=0;i<dataSize;i++)
			vab->buffer->push_back(data[i]);

		return true;
	}

	bool Mesh::isBufferAvailable(int bufferType)
	{
		if(bufferType<0)
			return false;

		//Check to see if there is already data for this particular array
		int arrays = dataBuffers.size();
		VertexArrayBuffer *vec = dataBuffers[bufferType]; 

		//If a vector was not inserted, there was already data in the array
		if(dataBuffers.size() == arrays && vec!=NULL)	
			return true;
		else
		{
			dataBuffers[bufferType]=NULL;
			return false;
		}
	}

	void Mesh::shareVerts()
	{
		//create the new vertex buffers
		vector< vector<float>* > newBuffers = vector< vector<float>* >();
		for(int i=0;i<bufferKeys.size();i++)
		{
			newBuffers.push_back(new vector<float>());
			newBuffers[i]->clear();
		}

		int newVerts=0;

		//Go through all the vertices and see if there are duplicates
		for(int i=0;i<verts;i++)
		{
			//For each vertex, check to see if it is the same as one of the vertices in the new vertex list
			//At the end of the for loop, if there was a match, then testIndex is the index of the vertex
			//in the new vertex list that the vertex under test matches
			bool match = false;
			int testIndex;
			for(testIndex=0;testIndex<newVerts && !match;testIndex++)
				match = isSameVert(i,testIndex,newBuffers);

			//decrement testIndex because it automatically gets incremented one too many times in the for loop
			testIndex--;
			//If the vertex matches one of the ones that are already in the list,
			//then point all the faces that used that vertex to the one that it matches
            if(match)
			{
				for(unsigned int faceIndex=0;faceIndex<(faces*vertsPerFace);faceIndex++)
				{
					if((*faceData)[faceIndex]==i)
					{
						(*faceData)[faceIndex]=testIndex;
						int newIndex = (*faceData)[faceIndex];
					}
				}
			}
			//If the vertex does not match any of them, then add it to the list and update
			//the face data with the new index of the old vertex
			else
			{
				for(int key=0;key<bufferKeys.size();key++)
				{
					vector<float> *newBuffer = newBuffers[key];
					VertexArrayBuffer *curBufferArray = dataBuffers[bufferKeys[key]];
					vector<float> *curBuffer = curBufferArray->buffer;

					//The vertex matches only if all components of all vertex buffers for that vertex match
					int ppVertex = curBufferArray->partsPerVertex;
					for(int j=0;j<ppVertex;j++)
					{
						newBuffer->push_back((*curBuffer)[i*ppVertex+j]);
					}
				}
				
				for(int faceIndex=0;faceIndex<(faces*vertsPerFace);faceIndex++)
				{
					if((*faceData)[faceIndex]==i)
						(*faceData)[faceIndex]=newVerts;
				}

				newVerts++;
			}
		}

		//This needs to be set before set the new data buffers because
		//addDataBuffer will only add verts number of vertices
		verts = newVerts;

		for(int i=0;i<newBuffers.size();i++)
		{
			int currentBuffer = bufferKeys[i];
			vector<float> *newData = newBuffers[i];
			replaceDataBuffer(currentBuffer, &(*newData)[0]);
			delete newData;
		}
	}

	bool Mesh::isSameVert(unsigned int currentIndex,unsigned int testIndex,vector< vector<float>* > newBuffers)
	{
		//For each buffer in the dataBuffers map, compare to the list of new buffers
		for(int currentBuffer=0; currentBuffer < bufferKeys.size(); currentBuffer++)
		{
			vector<float> *testBuffer = newBuffers[currentBuffer];

			VertexArrayBuffer *curBufferArray = dataBuffers[bufferKeys[currentBuffer]];

			vector<float> *curBuffer = curBufferArray->buffer;

			//The vertex matches only if all components of all vertex buffers for that vertex match
			int ppVertex = curBufferArray->partsPerVertex;
			for(int i=0;i<ppVertex;i++)
			{
				if((*curBuffer)[currentIndex*ppVertex+i] != (*testBuffer)[testIndex*ppVertex+i])
					return false;
			}
		}

		return true;
	}

	void Mesh::unShareVerts()
	{
		int newVerts=faces*vertsPerFace;

		//create the new vertex buffers
		vector< vector<float>* > newBuffers = vector< vector<float>* >();
		for(int i=0;i<bufferKeys.size();i++)
		{
			newBuffers.push_back(new vector<float>());
			newBuffers[i]->clear();
		}

		for(unsigned int i=0;i<faces*vertsPerFace;i++)
		{
			int curVert = (*faceData)[i];
			(*faceData)[i]=i;

			for(int j=0;j<newBuffers.size();j++)
			{
				VertexArrayBuffer *vab = dataBuffers[bufferKeys[j]];
				vector<float> *buffer = vab->buffer;
				vector<float> *newBuffer = newBuffers[j];

				int ppVertex = vab->partsPerVertex;
				for(int part=0;part<ppVertex;part++)
				{
					float newVertPart = (*buffer)[curVert * ppVertex + part];
					newBuffer->push_back(newVertPart);
				}
			}
		}

		//This needs to be set before set the new data buffers because
		//addDataBuffer will only add verts number of vertices
		verts = newVerts;

		for(int i=0;i<newBuffers.size();i++)
		{
			int currentBuffer = bufferKeys[i];
			vector<float> *newData = newBuffers[i];
			replaceDataBuffer(currentBuffer, &(*newData)[0]);
			delete newData;
		}
	}

	int Mesh::getVertsPerFace()
	{
		return vertsPerFace;
	}

	int Mesh::getNumberOfVerts()
	{
		return verts;
	}

	int Mesh::getNumberOfFaces()
	{
		return faces;
	}

	int Mesh::getElementsPerVertex(int bufferType)
	{
		if(!isBufferAvailable(bufferType))
			return -1;

		return dataBuffers[bufferType]->partsPerVertex;
	}

	vector<float> *Mesh::getData(int bufferType){
		if(!isBufferAvailable(bufferType))
			return NULL;

		return dataBuffers[bufferType]->buffer;
	}

	float *Mesh::getRawDataBuffer(int bufferType){
		vector<float> *data = getData(bufferType);
		if(data==NULL)
			return NULL;

		return &(*data)[0];
	}

	vector<unsigned int> *Mesh::getFaceData()
	{
		return faceData;
	}

	unsigned int *Mesh::getRawFaceDataBuffer()
	{
		vector<unsigned int> *data = getFaceData();

		return &(*data)[0];
	}

	vector<float> *Mesh::getVertData()
	{
		return dataBuffers[VERTEX_BUFFER]->buffer;
	}

	float *Mesh::getRawVertDataBuffer()
	{
		vector<float> *data = getVertData();

		return &(*data)[0];
	}

}